<?php
/**
 * Mailview
 * http://code.google.com/p/mailview/
 * Martin Tournoij <martin@arp242.net>
 * Mailview is free for any use, there are no restrictions.
 *
 * Functions to connect to the mail server.
 *
 * Most of these functions have the same name as the imap* functions. This 
 * allows us some abstraction without introducing a whole new API of sorts.
 *
 * These functions are in the Mailview name space. If you want to call them 
 * use Mailview\function(); If you want to call the original function, use 
 * \function();
 *
 * NOTE: We *always* use UIDs instead of message numbers. (ST|FT)_UID is added 
 * automaticlly for most functions. The exception is imap_fetch_overview(), 
 * where using msgno makes more sense.
 */

namespace Mailview;

/**
 * Open link to mailserver
 * Note: default $n_retries is 1, this is different from the default (3).
 *
 * @param array $config Configuration array
 *
 * @return object
 */
function imap_open($config, $options=Null, $n_retries=1, $params=array(), $test=False)
{
	$flags = '';

	# More realistic timeouts. They default to 60 seconds.
	\imap_timeout(IMAP_OPENTIMEOUT, 5);
	\imap_timeout(IMAP_READTIMEOUT, 5);
	\imap_timeout(IMAP_WRITETIMEOUT, 5);
	\imap_timeout(IMAP_CLOSETIMEOUT, 5);

	if (!isset($_SESSION['ref']) or $_SESSION['ref'] === '')
	{
		$uri = parse_url($config['server']);

		if ($uri['scheme'] === 'imap' or $uri['scheme'] === 'imaps')
			$flags .= '/service=imap';
		elseif ($uri['scheme'] === 'pop3' or $uri['scheme'] === 'pop3s')
			$flags .= '/service=pop3';
		else
			trigger_error(sprintf('Unknown protocol "%s".', $m['1']), E_USER_ERROR);

		if ($uri['scheme'] === 'imaps' or $uri['scheme'] === 'pop3s')
		{
			if ((int) $config['usetls'] === 1 or $config['usetls'] === 'on')
				$flags .= '/tls';
			else
				$flags .= '/ssl';

			if ((int) $config['validatessl'] === 1 or $config['validatessl'] === 'on')
				$flags .= '/validate-cert';
			else
				$flags .= '/novalidate-cert';
		}

		$_SESSION['ref'] = sprintf('{%s:%s%s}', $uri['host'], $uri['port'], $flags);
		$_SESSION['hostname'] = $uri['host'];
		$_SESSION['port'] = $uri['port'];
		$_SESSION['protocol'] = $uri['scheme'];
	}
	if (!isset($_SESSION['mailbox']))
		$_SESSION['mailbox'] = 'INBOX';
	elseif (isset($_GET['mailbox']))
		$_SESSION['mailbox'] = $_GET['mailbox'];

	$link = @\imap_open($_SESSION['ref'] . $_SESSION['mailbox'], 
		$config['username'], $config['password'], $options, $n_retries, $params);

	if (!$link)
	{
		if ($test)
			return False;
		else
		{
			$ref = $_SESSION['ref'];
			$_SESSION = array();
			trigger_error(sprintf('Error connecting to "%s", %s',
				$ref, imap_last_error()), E_USER_ERROR);
		}
	}

	return $link;
}

/**
 * Check login info and try and provide an meaningfull message as to
 * what went wrong
 *
 * We also check if the connection supports IDLE
 *
 * @return If an error occured we return a string, if no error occured and the 
 * server does NOT support IDLE, we return False, if no error occured and the 
 * server does support IDLE, we return True
 *
 * (IDLE, obviously, only works in IMAP)
 */
function TestImap($config)
{
	$idle = False;
	$uri = parse_url($config['server']);

	# Attempt a basic connection
	if ($uri['scheme'] === 'imaps' or $uri['scheme'] === 'pop3s')
	{
		$socket = @fsockopen($config['usetls'] ? 'tls://' : 'ssl://'
			. gethostbyname($uri['host']), $uri['port'], $errno, $errstr, 3);
	}
	else
		$socket = @fsockopen(gethostbyname($uri['host']), $uri['port'], $errno, $errstr, 3);

	if (!$socket)
	{
		return sprintf('Unable to open connection to %s -- Error %s: %s',
			$config['server'], $errno, $errstr);
	}

	stream_set_timeout($socket, 3);

	# IMAP login
	if ($uri['scheme'] === 'imap' or $uri['scheme'] === 'imaps')
	{
		$msg = fgets($socket);
		if ($msg === False or substr($msg, 0, 4) !== '* OK')
		{
			@fclose($socket);
			return "Unexpected answer from server: $msg\n";
		}

		fwrite($socket, ". LOGIN {$config['username']} {$config['password']}\n");
		$msg = fgets($socket);
		if ($msg === False or substr($msg, 0, 4) !== '. OK')
		{
			@fclose($socket);
			return "Unable to login: $msg\n";
		}

		# CAPABILITY
		$st = strpos($msg, '[');
		$msg = explode(' ', substr($msg, $st, strrpos($msg, ']') - $st));

		if (in_array('IDLE', $msg))
			$idle = True;
	}
	# POP3 login
	elseif ($uri['scheme'] === 'pop3' or $uri['scheme'] === 'pop3s')
	{
		$msg = fgets($socket);
		if ($msg === False or substr($msg, 0, 3) !== '+OK')
		{
			@fclose($socket);
			return "Unexpected answer from server: $msg\n";
		}

		fwrite($socket, "USER {$config['username']}\n");
		$msg = fgets($socket);
		if ($msg === False or substr($msg, 0, 3) !== '+OK')
		{
			@fclose($socket);
			return "Unable to login: $msg\n";
		}

		fwrite($socket, "PASS {$config['password']}\n");
		$msg = fgets($socket);
		if ($msg === False or substr($msg, 0, 3) !== '+OK')
		{
			@fclose($socket);
			return "Unable to login: $msg\n";
		}
	}
	fclose($socket);

	# So, basic settings worked -- Now let's try imap_open()
	if ($uri['scheme'] === 'imaps' or $uri['scheme'] === 'imap')
		$c = imap_open($config, OP_HALFOPEN, 1, array(), True);
	else
		$c = imap_open($config, Null, 1, array(), True);

	@imap_close($c);
	if (!$c)
		return 'imap_open() error: ' . imap_last_error();

	if ($idle)
		return True;
	else
		return False;
}

function imap_reopen($link, $mailbox, $options=0)
{
	$_SESSION['mailbox'] = $mailbox;
	return \imap_reopen($link, $mailbox, $options);
}

/**
 * Return last error
 */
function imap_last_error()
{
	return \imap_last_error();
}

/**
 * Get number of messages in current mailbox
 */
function imap_num_msg($link)
{
	return \imap_num_msg($link);
}

/**
 * Get number of recent messages in current mailbox
 * NOTE: recent != unseen
 */
function imap_num_recent($link)
{
	return \imap_num_recent($link);
}

/**
 * Get information about the current mailbox
 * NOTE: this is pretty slow, does a FETCH on every message in the mailbox. 
 * use imap_status()
 */
function imap_mailboxmsginfo($link)
{
	return \imap_mailboxmsginfo($link);
}

/**
 * Gets status information about the given mailbox.
 */
function imap_status($link, $mailbox, $options)
{
	return \imap_status($link, $mailbox, $options);
}

/**
 *
 */
function imap_fetch_overview($link, $sequence, $options=0)
{
	$overview = \imap_fetch_overview($link, $sequence, $options);

	foreach ($overview as &$msg)
	{
		$tmp = @iconv_mime_decode($msg->subject);
		if ($tmp !== False)
			$msg->subject = $tmp;

		# Grrrrrrrrrrrrr
		if (!isset($msg->subject) or trim($msg->subject) === '')
			$msg->subject = '[ Empty subject ]';

		if (isset($msg->date))
			$msg->date = date($_SESSION['config']['briefdateformat'], strtotime($msg->date));
		else
			$msg->date = '';

		$tmp = @iconv_mime_decode($msg->from);
		if ($tmp !== False)
			$msg->from = trim(preg_replace('/<.*>|"/', '', $tmp));

		$msg->flags = '';
		if ($msg->flagged)
			$msg->flags .= '!';
		if ($msg->answered)
			$msg->flags .= 'r';
		if ($msg->deleted)
			$msg->flags .= 'D';
		if (!$msg->seen)
			$msg->flags .= 'N';
	}

	return $overview;
}

/**
 * Sort a structure as returned by imap_fetch_overview()
 */
function SortOverview(&$overview, $order)
{
	$order = trim($order);

	if (substr($order, 0, 8) === 'reverse-')
	{
		$reverse = True;
		$order = substr($order, 8);
	}
	else
		$reverse = False;

	# Date: Numeric comparison
	if ($order === 'date')
	{
		$order = 'udate';

		usort($overview, function($a, $b) use ($order)
		{
			if ($a->$order > $b->$order)
				return 1;
			elseif ($a->$order === $b->$order)
				return 0;
			else
				return -1;
		});
	}
	# Everything else: String comparison
	else
	{
		usort($overview, function($a, $b) use ($order)
		{
			return strcmp($a->$order, $b->$order);
		});
	}

	# XXX Is using return $reverse ? 1 : -1; in usort() significantly faster? Test this.
	if ($reverse)
		$overview = array_reverse($overview);
}

/**
 *
 */
function imap_list($link, $ref, $pattern)
{
	return \imap_list($link, $ref, $pattern);
}

/**
 *
 */
function imap_lsub($link, $ref, $pattern)
{
	return \imap_lsub($link, $ref, $pattern);
}

/**
 *
 */
function imap_close($link)
{
	return \imap_close($link);
}

/**
 *
 */
function imap_fetchbody($link, $msgno, $section, $options=0)
{
	$options |= FT_UID;
	return \imap_fetchbody($link, $msgno, $section, $options);
}

/**
 */
function imap_fetchheader($link, $msgno, $proc=True, $options=0)
{
	$options |= FT_UID;

	if (!$proc)
		return \imap_fetchheader($link, $msgno, $options);

	$header = ParseHeaders(\imap_fetchheader($link, $msgno, $options));

	if (isset($header['subject']))
		$header['subject'] = @iconv_mime_decode($header['subject']);
	if (isset($header['from']))
		$header['from'] = @iconv_mime_decode($header['from']);
	if (isset($header['to']))
		$header['to'] = @iconv_mime_decode($header['to']);
	if (isset($header['cc']))
		$header['cc'] = @iconv_mime_decode($header['cc']);

	return $header;
}

/**
 * Like imap_rfc822_parse_headers, but returns *all* headers
 * 
 * @param string $headers String with headers
 *
 * @return array Array with key/value pair. Keys are lowercased.
 */
function ParseHeaders($headers)
{
	$h = explode("\n", $headers);

	$pheaders = array();
	foreach ($h as $line)
	{
		if ($line === '')
			continue;

		if ($line[0] === "\t" or $line[0] === ' ')
			$pheaders["$key"] .= " " . trim($line);
		else
		{
			$p = strpos($line, ':');
			$key = strtolower(trim(substr($line, 0, $p)));
			$value = trim(substr($line, $p + 1));

			#if ($value !== '')
			{
				if (isset($pheaders["$key"]))
					$pheaders["$key"]	.= "\n" . $value;
				else
					$pheaders["$key"] = $value;
			}
		}
	}

	return $pheaders;
}

function imap_body($link, $msgno, $proc=True, $options=0)
{
	$options |= FT_UID;

	if (!$proc)
		return \imap_body($link, $msgno, $options);

	$body = \imap_body($link, $msgno, $options);

	$body = explode("\n", $body);

	return $body;
}

function imap_fetchstructure($link, $msgno, $options=0)
{
	$options |= FT_UID;

	return \imap_fetchstructure($link, $msgno, $options);
}

/**
 *
 */
function imap_setflag_full($link, $sequence, $flag, $options=0)
{
	$options |= ST_UID;

	return \imap_setflag_full($link, $sequence, $flag, $options);
}

/**
 *
 */
function imap_clearflag_full($link, $sequence, $flag, $options=0)
{
	$options |= ST_UID;

	return \imap_clearflag_full($link, $sequence, $flag, $options);
}

/**
 *
 */
function imap_mail_copy($link, $msglist, $mailbox, $options=0)
{
	$options |= CP_UID;

	$r = \imap_mail_copy($link, $msglist, $mailbox, $options);
	if (!$r)
		trigger_error(imap_last_error(), E_USER_ERROR);
}

/**
 *
 */
function imap_mail_move($link, $msglist, $mailbox, $options=0)
{
	$options |= CP_UID;

	$r = \imap_mail_move($link, $msglist, $mailbox, $options);
	if (!$r)
		trigger_error(imap_last_error(), E_USER_ERROR);
}

/**
 *
 */
function imap_expunge($link)
{
	return \imap_expunge($link);
}

/**
 *
 */
/*
function iconv_mime_decode($header, $mode=0, $charset=Null)
{
	if ($charset === Null)
		$charset = ini_get('iconv.internal_encoding');

	$r = @iconv_mime_decode($header, $mode, $charset);

	if ($r === False)
		return $header;
	else
		return $r;
}
*/


/**
 * Returns array of MIME parts
 */
function MailParts($struct, $parent='')
{
	# Start at 1 because part number 0 is the header
	$i = 1;
	$return = array();
	foreach ($struct->parts as $p)
	{
		# Multipart
		if ($p->type === 1)
			$return = array_merge($return, MailParts($p, ($parent !== '' ? "$parent." : '') . $i));
		else
		{
			$p->partno = ($parent !== '' ? "$parent." : '') . $i;
			$return["$p->partno"] = $p;
		}
		
		$i += 1;
	}

	return $return;
}
/**
 * Decode mail transfer encodings
 */
function MailDecode($string, $encoding)
{
	# 7-bit ascii
	if ($encoding === 0)
		$r = $string;
	# 8-bit ascii
	elseif ($encoding === 1)
		$r = $string;
	# binary
	elseif ($encoding === 2)
		$r = $string;
	# base64
	elseif ($encoding === 3)
		$r = base64_decode($string);
	# quoted-printable
	elseif ($encoding === 4)
		$r = quoted_printable_decode($string);
	# other
	elseif ($encoding === 5)
		$r = $string;
	# unknown
	else
		$r = $string;

	return $r;
}

/**
 * Convert mime numbers as used by imap_* to text
 */
function Mime($p)
{
	# Text
	if ($p->type === 0)
	{
		$mime['0'] = 'text';
		$mime['1'] = True;
	}
	# Multipart
	elseif ($p->type === 1)
	{
		$mime['0'] = 'multipart';
		$mime['1'] = True;
	}
	# Message
	elseif ($p->type === 2)
	{
		$mime['0'] = 'message';
		$mime['1'] = True;
	}
	# Application
	elseif ($p->type === 3)
	{
		$mime['0'] = 'application';
		$mime['1'] = False;
	}
	# Audio
	elseif ($p->type === 4)
	{
		$mime['0'] = 'audio';
		$mime['1'] = False;
	}
	# Image
	elseif ($p->type === 5)
	{
		$mime['0'] = 'image';
		$mime['1'] = True;
	}
	# Video
	elseif ($p->type === 6)
	{
		$mime['0'] = 'video';
		$mime['1'] = False;
	}
	# Other
	elseif ($p->type === 7)
	{
		$mime['0'] = 'other';
		$mime['1'] = False;
	}

	return $mime;
}

/**
 * Search for specific parameter as returned by imap_fetchstructure() and return the value (if any)
 */
function SearchParameter($struct, $name)
{
	if ($struct->ifparameters)
	{
		foreach ($struct->parameters as $p)
		{
			if ($p->attribute === $name)
				return $p->value;
		}
	}

	if ($struct->ifdparameters)
	{
		foreach ($struct->dparameters as $p)
		{
			if ($p->attribute === $name)
				return $p->value;
		}
	}
	
	return False;
}

/**
 * Make text readable and ready for printing
 *
 * XXX Could be a bit better
 */
function MailText($link, $part, $colorquotes=True)
{
	$body = MailDecode(imap_fetchbody($link, (int) $_GET['uid'], $part->partno, FT_PEEK), $part->encoding);

	# Convert to correct character set
	if ($charset = SearchParameter($part, 'charset'))
	{
		# Complain when iconv doesn't support the character set (unlikely) or when 
		# the specified character set is inavlid (More likely).

		$body = @iconv($charset, $_SESSION['config']['charset'] . '//IGNORE', $body);
		if ($body === False)
		{
			$body = "Something went very wrong when trying to read this email and convert it to {$_SESSION['config']['charset']}
				This usually means there is a problem in the email. The emails reports to be {$charset}";
		}
	}

	if ($colorquotes)
	{
		# Colorize quoted stuff (i.e. lines starting with > character)
		$body = explode("\n", $body);

		$spanopen = False;
		$signature = False;
		$newbody = '';
		foreach ($body as $l)
		{
			if (!$signature and isset($l[0]) and ($l[0] === '>' or $l[0] === '|') and !$spanopen)
			{
				$newbody .= '<span class="quote">' . htmlentities("$l\n", ENT_COMPAT, $_SESSION['config']['charset']);
				$spanopen = True;
			}
			elseif (!$signature and isset($l[0]) and ($l[0] !== '>' and $l[0] !== '|') and $spanopen)
			{
				$newbody .= '</span>' . htmlentities("$l\n", ENT_COMPAT, $_SESSION['config']['charset']);
				$spanopen = False;
			}
			// Signature
			elseif (rtrim($l, "\r\n") === '-- ')
			{
				if ($spanopen)
					$newbody .= '</span>';

				$signature = True;
				$newbody .= '<span class="signature">' . $l;
			}
			else
				$newbody .= htmlspecialchars("$l\n", ENT_COMPAT, $_SESSION['config']['charset']);
		}
		if ($spanopen)
			$newbody .= '</span>';
		if ($signature)
			$newbody .= '</span>';

		$body = $newbody;

		# Smell links and put <a> tags around it.
		# I copied this from  "http://www.php.net/manual/en/function.strip-tags.php" 
		# Posted by "lucky760 at yahoo dot com" 
		# XXX links without protocol (ie no 'http://', like 'www.foobar.com') are not smelled.
		$body = preg_replace('!(^|([^\'"]\s*))' . '([hf][tps]{2,4}:\/\/[^\s<>"\'()]{4,})!mi', '$2<a href="$3">$3</a>', $body);
		$body = preg_replace('!<a href="([^"]+)[\.:,\]]">!', '<a href="$1">', $body);
		$body = preg_replace('!([\.:,\]])</a>!', '</a>$1', $body);

		# Detect email addresses
		$body = preg_replace('! \b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}\b !mi', '<a href="mailto:$0">$0</a>', $body);
	}

	return $body;
}

function Warning($msg)
{
	return "\n\n** WARNING **: $msg\n";
}

/**
 * Parse a string of addresses and put them in an array Swift Mailer can understand
 *
 * XXX imap_rfc822_parse_adrlist() isn't very good according to their owns 
 * docs. Re-implement this (?)
 */
function ParseAddress($adr)
{
	$pard = \imap_rfc822_parse_adrlist($adr, '');

	$ret = array();
	foreach ($pard as $a)
	{
		# XXX Proper errors!
		if (!isset($a->mailbox) or !isset($a->host))
		{
			print 'Cannot parse address: ' . $adr;
			exit(1);
		}

		$mail = $a->mailbox . '@' . $a->host;
		if (isset($a->personal))
			$ret["$mail"] = $a->personal;
		else
			$ret[] = $mail;
	}

	return $ret;
}

/**
 * Read iCalendar attachment
 *
 * TODO: This is a very quick hack
 */
function ParseiCal($str)
{
	$str = explode("\n", str_replace("\r\n", "\n", $str));

	$ret = array();
	$blocks = array();
	foreach ($str as $line)
	{
		if (!stristr($line, ':'))
			continue;

		list($rkey, $value) = explode(':', $line);
		$attr = explode(';', $rkey);
		$key = array_shift($attr);
		$key = trim(strtolower($key));

		if ($key === 'begin')
			array_push($blocks, strtolower(trim($value)));
		elseif ($key === 'end')
			array_pop($blocks);
		elseif ($blocks[count($blocks) - 1] === 'vevent')
		{
			if ($key === 'description')
				$ret['description'] = str_replace('\N', "\n", trim(str_replace(array('<<', '>>'), '', $value)));
			elseif ($key === 'dtstart')
			{
				$tz = array_shift($attr);
				preg_match('/\((.*)\)/', $tz, $m);
				$tz = $m['1'];

				$ret['start'] = date($_SESSION['config']['dateformat'], strtotime($value));
			}
			elseif ($key === 'dtend')
			{
				$tz = array_shift($attr);
				preg_match('/\((.*)\)/', $tz, $m);
				$tz = $m['1'];

				$ret['end'] = date($_SESSION['config']['dateformat'], strtotime($value));
			}
		}
	}

	return $ret;
}

/**
 * This is a reimplementation of the imap_thread() function
 *
 * @todo
 */
function imap_thread($messages, $method)
{
}

?>
